const vscode = require('vscode');
const { kernel32, user32, getMessage, enumWindows, enumChildWindows, findProcessesByFilename, launchApplication, findPortsByProcessId, getAppIcon } = require('./functions/window_api');
const path = require('path');
const fs = require('fs');
const { loadIni, saveIni } = require('./config/ServerConfigIni');
const EventEmitter = require('events');

const STATUS_IDLE = 0;
const STATUS_STARTING = 1;
const STATUS_LAUNCHED = 2;
const STATUS_STOPING = 3;
const STATUS_WAITING = 4;

/**
 * 
 * @typedef {'DBServer'|'LoginSrv'|'LogServer'|'M2Server'|'RunGate'|'SelGate'|'LoginGate'} ServerName
 * 
 * @typedef {{ ClassName:string, processID:number, text:string, hwnd:number, lParam: any }} WindowInfo
 * 
 * @typedef {{processId:number, windows:{main:WindowInfo, message:WindowInfo}, ports:number[]}} ServerProcess
 * @typedef {{filename:string,title:string,name:ServerName,dir:string,basename:string,number:number}} ServerFile
 * 
 */


function sleep(delay = 0) {
    return new Promise((resolve) => setTimeout(resolve, delay));
}

/**
 * @extends {EventEmitter<{
 *   onLaunchAllLaunchBefore:[],
 *   onServerAllLaunchAfter:[],
 *   onLaunchBefore:[filename:string],
 *   onLaunchAfter:[filename:string,processId:number],
 *   findWindows:[serverName:ServerName|string, serverProcess:ServerProcess],
 * }>}
 */

class GameServers extends EventEmitter {

    /** @type {Record<ServerName,ServerProcess[]>} */
    processes = {}
    /** @type {vscode.ExtensionContext} */
    context = null;
    serverNames = {
        'DBServer': "数据库服务",
        'LoginSrv': "账号登录服务",
        'LogServer': "日志服务",
        'M2Server': "M2主服务",
        'RunGate': "运行网关",
        'SelGate': "角色网关",
        'LoginGate': "登录网关"
    }
    /** @type {Record<ServerName,Record<string,string>>|null} */
    rootConfig = null

    /** @param {vscode.ExtensionContext} context  */
    constructor(context) {
        super();
        this.context = context;
        this.loadRootConfig();
        this.initServerFile();
        this.registerStatusFunc();
        this.registerConsoleFunc();
        this.updateServerStatus();
    }


    loadRootConfig() {
        this.config = vscode.workspace.getConfiguration('mir-script-config');
        this.rootPath = this.config.get("RootPath");
        if (!this.rootPath || fs.existsSync(this.rootPath) === false) {
            vscode.window.showErrorMessage('请先设置根目录');
            return
        }
        const configFilename = path.join(this.rootPath, 'Config.ini');
        if (fs.existsSync(configFilename) === false) {
            vscode.window.showErrorMessage(`${configFilename}不存在!`);
            return
        }
        this.rootConfig = loadIni(configFilename);
    }

    initServerFile() {
        /** @type {ServerFile[]} */
        this.serverFiles = []
        for (const name in this.serverNames) {
            if (!Object.prototype.hasOwnProperty.call(this.serverNames, name)) {
                continue
            }
            const filename = this.config.get(`${name}File`);
            if (!filename) {
                continue
            }
            const file = { filename, name, title: this.serverNames[name], dir: "", basename: '', number: 1 }
            if (name === "RunGate") {
                file.number = parseInt(this.rootConfig?.RunGate?.Count || 1)
            }
            if (fs.existsSync(filename)) {
                file.dir = path.dirname(filename)
                file.basename = path.basename(filename)
            }
            this.serverFiles.push(file)
        }

        this.config.get("OtherServerFile").forEach(line => {
            const arr = line.split("|")
            const filename = arr[0]?.trim()
            if (!filename) {
                return
            }
            const file = { filename, title: arr[1]?.trim(), name: arr[2]?.trim(), dir: '', basename: '', number: 1 }
            if (fs.existsSync(filename)) {
                const pathinfo = path.parse(filename)
                file.dir = pathinfo.dir
                file.basename = pathinfo.base
                if (!file.name) {
                    file.name = pathinfo.name
                }
                if (!file.title) {
                    file.title = pathinfo.name
                }
            }
            this.serverFiles.push(file)
        });

    }

    registerStatusFunc() {
        this.commandStart = "extension.mir-script-plugin.startServer"
        const commandStart = vscode.commands.registerCommand(this.commandStart, async () => {
            vscode.window.showInformationMessage('确定要启动所有服务吗？', { modal: true }, "确定").then(async (value) => {
                if (value === "确定") {
                    this.updateStatusBar(STATUS_STARTING)
                    this.emit("onLaunchLaunchBefore")
                    await this.startServers();
                    await this.updateServerStatus()
                    this.emit("onServerLaunchAfter")
                }
            });
        });
        this.context.subscriptions.push(commandStart);

        this.commandStop = "extension.mir-script-plugin.stopServer"
        const commandStop = vscode.commands.registerCommand(this.commandStop, () => {
            vscode.window.showInformationMessage('确定要停止所有服务吗？', { modal: true }, "确定").then(async (value) => {
                if (value === "确定") {
                    this.closeServers()
                }
            });
        });
        this.context.subscriptions.push(commandStop);
        this.statusBar = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
        this.updateStatusBar(STATUS_IDLE);
    }
    registerConsoleFunc() {
        this.outputChannel = vscode.window.createOutputChannel('M2日志');
        this.commandConsole = 'extension.mir-script-plugin.consolelog'
        const consolelogFunction = vscode.commands.registerCommand(this.commandConsole, () => {
            this.outputChannel.show();
        });
        this.context.subscriptions.push(consolelogFunction);

        this.statusBarConsole = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);
        this.statusBarConsole.text = 'M2日志'
        this.statusBarConsole.command = this.commandConsole
    }



    async updateServerStatus() {
        this.updateStatusBar(STATUS_WAITING)
        const count = { number: 0, launch: 0 }
        const allProcesses = []

        // 预先查询到所有服务的进程，减少枚举窗口的次数
        for (let index = 0; index < this.serverFiles.length; index++) {
            const serverFile = this.serverFiles[index];
            const filename = serverFile.filename;
            // 跳过未配置的服务
            if (!filename) continue;
            // 文件不存在提示
            if (fs.existsSync(filename) === false) {
                vscode.window.showErrorMessage(`文件不存在：${filename}`);
                continue;
            }
            count.number += serverFile.number
            const processItem = findProcessesByFilename(serverFile.filename).map(v => ({ id: parseInt(v.ProcessId), name: serverFile.name }))
            allProcesses.push(...processItem)
        }
        // 只枚举一遍窗口，并且找到就停
        const windows = await this.filterWindowsByProcessIds(allProcesses.map(v => v.id))
        this.processes = {}
        allProcesses.forEach(item => {
            if (windows[item.id]) {
                const serverProcess = { processId: item.id, windows: windows[item.id] }
                if (!this.processes[item.name]) {
                    this.processes[item.name] = []
                    this.emit("findWindows", item.name, serverProcess)
                }
                this.processes[item.name].push(serverProcess)
                count.launch++
            }
        });

        if (count.launch < count.number) {
            this.updateStatusBar(STATUS_IDLE, count.launch > 0 ? '部分服务未启动，立即启动？' : '启动所有服务')
        } else {
            this.updateStatusBar(STATUS_LAUNCHED)
        }
        this.subscribeM2Log()
    }

    async startServers() {
        this.loadRootConfig();
        this.initServerFile();

        const launched = []
        for (let index = 0; index < this.serverFiles.length; index++) {
            const server = this.serverFiles[index];
            if (!server.filename || fs.existsSync(server.filename) === false) {
                vscode.window.showErrorMessage(`${server.title || server.name}启动失败，文件不存在!`);
                continue;
            }
            const startFunc = `start${server.name}`
            if (typeof this[startFunc] === 'function') {
                let result = this[startFunc](server.filename)
                if (result instanceof Promise) {
                    await result
                }
            } else {
                this.processes[server.name] = []
                const processInfoList = findProcessesByFilename(server.filename)
                if (processInfoList.length > 0) {
                    continue
                }
                const processID = await launchApplication(server.filename, server.dir, 200)
                if (!processID) {
                    vscode.window.showErrorMessage(`${server.title || server.name}启动失败!`);
                    continue
                }
            }
            launched.push(server.name)
        }
        return launched
    }

    /**
     * 
     * @param {string} filename 
     * @returns {Promise<number[] | undefined>}
     */
    async startRunGate(filename) {
        if (!filename || fs.existsSync(filename) === false) {
            vscode.window.showErrorMessage(`游戏网关启动失败，文件不存在!`);
            return
        }
        const workingDirectory = path.dirname(filename);
        const configFilename = path.join(workingDirectory, 'Config.ini');
        const config = loadIni(configFilename)
        const processInfoList = findProcessesByFilename(filename).map(info => {
            const Ports = findPortsByProcessId(info.ProcessId)
            return { ...info, Ports }
        })
        this.processes.RunGate = []
        const ids = []
        for (let index = 1; index <= this.rootConfig.RunGate.Count; index++) {
            config.GameGate.GatePort = this.rootConfig.RunGate[`GatePort${index}`]
            const info = processInfoList.find(v => v.Ports.includes(parseInt(config.GameGate.GatePort)))
            if (info) {
                // console.log(`已启动：RunGate-${config.GameGate.GatePort}`);
                ids.push(info.ProcessId)
                continue
            }
            config.GameGate.DBPort = this.rootConfig.RunGate[`DBPort${index}`]
            config.GameGate.Title = config.GameGate.Title.replace(/\((\d+)\)$/, `(${config.GameGate.GatePort})`)
            saveIni(configFilename, config)
            const processId = await launchApplication(filename, workingDirectory, 200)
            if (!processId) {
                vscode.window.showErrorMessage(`游戏网关启动失败，请检查配置文件!`);
                continue
            }
            ids.push(processId)
        }
        return ids
    }

    async closeServers() {
        this.updateStatusBar(STATUS_STOPING)
        const names = Object.keys(this.processes)
        for (let index = names.length - 1; index >= 0; index--) {
            const name = names[index];
            if (!Object.hasOwnProperty.call(this.processes, name) || this.processes[name].length === 0) {
                continue
            }
            while (this.processes[name].length > 0) {
                /** @type {ServerProcess} */
                const item = this.processes[name].pop();
                user32.PostMessageA(item.windows.main.hwnd, 0x0010, 0, 0);
                await sleep(100)
                const confirmWindow = await enumWindows((info) => {
                    if (info.processId === item.processId && info.className === "#32770" && /.*信息.*/.test(info.text)) {
                        return true
                    }
                })
                if (confirmWindow.length > 0) {
                    for (let ci = 0; ci < confirmWindow.length; ci++) {
                        user32.SendMessageA(confirmWindow[ci].hwnd, 0x0111, 6, Buffer.alloc(64));
                    }
                }

                // 等待进程退出
                const processHandle = kernel32.OpenProcess(0x00100000, false, item.processId);
                if (processHandle) {
                    const waitResult = kernel32.WaitForSingleObject(processHandle, 5000); // 等待最多5秒
                    if (waitResult !== 0) {
                        console.log(`等待进程 ${item.processId} 退出超时,${waitResult}`);
                    }
                    kernel32.CloseHandle(processHandle)
                } else {
                    console.log(`无法打开进程 ${item.processId}`);
                }
            }
        }
        this.updateServerStatus()
    }



    stopApplication(pid) {
        exec(`taskkill /PID ${pid} /F`, (error, stdout, stderr) => {
            if (error) {
                console.error(`执行出错: ${error.message}`);
                return;
            }
            if (stderr) {
                console.error(`错误输出: ${stderr}`);
                return;
            }
            console.log(`标准输出: ${stdout}`);
        });
    }



    subscribeM2Log() {
        if (!this.processes.M2Server?.length) {
            return
        }
        if (this.m2logTimer) {
            clearInterval(this.m2logTimer)
            this.m2logTimer = undefined
        }
        const messageWindow = this.processes.M2Server[0].windows.message
        this.statusBarConsole.show()

        let logs = ""
        this.m2logTimer = setInterval(async () => {
            const newLogs = getMessage(messageWindow.hwnd)
            if (logs === newLogs) {
                return
            }
            logs = newLogs
            const logsArr = logs.split("\n")
            this.outputChannel.clear();
            this.outputChannel.appendLine(logs);
            const updateLines = logsArr.slice(-2)
            for (let index = 0; index < updateLines.length; index++) {
                const line = updateLines[index];
                this.statusBarConsole.text = line.length > 30 ? line.substring(0, 30) + "..." : line
                await sleep(100)
            }
        }, 1000)
    }


    /**
     * @template T
     * @param {(index:number)=>Promise<T>|T} callback 
     * @param {number} delay 
     * @param {number} timeout 
     * @param {number} times 
     * @returns {Promise<T|null>}
     */
    task(callback, delay = 100, timeout = 1000, times = 0) {
        return new Promise(async (resolve) => {
            let isTimeout = false
            const timer = setTimeout(() => {
                isTimeout = true;
                console.log("超时");
                resolve(null)
            }, timeout)
            const total = times > 0 ? times : 2000
            let result = null
            let i = 0
            while (isTimeout === false && !result && i++ < total) {
                result = callback(i)
                if (result instanceof Promise) {
                    result = await result
                }
                if (result) {
                    break
                }
                await sleep(delay)
            }
            clearTimeout(timer)
            resolve(result)
        });
    }

    /**
     * 过滤程序窗口
     * @param {number[]} processIds 
     * @returns {Promise<Record<number, {main:WindowInfo,message:WindowInfo}>>}
     */
    async filterWindowsByProcessIds(processIds) {
        const windows = await enumWindows((handle) => {
            if (processIds.includes(handle.processId) && !!handle.text) {
                return handle
            }
        })
        const processWindows = {}
        for (let index = 0; index < windows.length; index++) {
            const window = windows[index]
            if (processWindows[window.processId]) {
                continue
            }
            // 找到消息窗口
            const messageWindow = await this.findChildMessageWindow(window.hwnd)
            if (messageWindow) {
                processWindows[window.processId] = { main: window, message: messageWindow }
            }
        }
        return processWindows
    }

    async findChildMessageWindow(hwnd) {
        const messageClassNameslist = ['TMemo', 'TRichEdit', 'TRichView', 'TJvRichEdit', 'TRzMemo'];
        const list = await enumChildWindows(hwnd, (handle) => messageClassNameslist.includes(handle.className))
        if (list.length > 0) {
            return list[0]
        }
        return null
    }


    updateStatusBar(status, tooltip = null) {
        switch (status) {
            case STATUS_IDLE:
                this.statusBar.text = '$(debug-start) 启动';
                this.statusBar.tooltip = tooltip || '启动服务器';
                this.statusBar.command = this.commandStart;
                break;
            case STATUS_STARTING:
                this.statusBar.text = '$(loading~spin)';
                this.statusBar.tooltip = tooltip || '正在启动服务器';
                break;
            case STATUS_LAUNCHED:
                this.statusBar.text = '$(debug-stop) 停止';
                this.statusBar.tooltip = tooltip || '关闭服务器';
                this.statusBar.command = this.commandStop;
                break;
            case STATUS_STOPING:
                this.statusBar.text = '$(loading~spin)';
                this.statusBar.tooltip = tooltip || '正在关闭服务器';
                break;
            case STATUS_WAITING:
                this.statusBar.text = '$(loading~spin)';
                this.statusBar.tooltip = tooltip || '等待任务执行...';
                break;
            default:
                break;
        }
        this.status = status
        this.statusBar.show();
    }
}

module.exports = GameServers;